package metagrammar;

import java.io.Serializable;
import java.util.UUID;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlList;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import jaxb.metagrammar.TypeToUUIDStringAdapter;
import jaxb.util.UUIDtoStringAdapter;
import util.HasId;
import util.Named;

import com.google.common.collect.ImmutableList;

/**
 * Class describing a type in a grammar.
 * A type is defined as having a name, which is a string (ideally human-readable)
 * which identifies it.
 * 
 * A type may also be a subtype of another type.
 * 
 * @author Joshua Lockerman
 * 
 */
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Type implements Typed, Serializable, HasId, Named {
    
    public static final Type ROOT = new Type("817f5988-3a46-4f5b-9263-ab58dbec452f", "root",
    "root type of all types");
    public static final Type UNTYPED = new Type("untyped");
    
    @XmlID
    @XmlAttribute
    @XmlJavaTypeAdapter(UUIDtoStringAdapter.class)
    private final UUID id;
    
    private final String name;
    private String description;
    
    @XmlJavaTypeAdapter(TypeToUUIDStringAdapter.class)
    @XmlList
    private Type[] superTypes;
    
    private Type()
    {
        // For (un)marshalling
        id = null;
        name = null;
    }
    
    private Type(String uuid, String name, String description)
    {
        id = UUID.fromString(uuid);
        this.name = name;
        this.description = description;
        superTypes = new Type[] {};
    }
    
    /**
     * Creates a Type with a given name.
     * 
     * @param name the name of the Type
     */
    public Type(String name)
    {
        id = UUID.randomUUID();
        this.name = name;
        superTypes = new Type[] { ROOT };
    }
    
    /**
     * Creates a Type with a given name that is a subtype of the given type(s).
     * 
     * @param name
     * @param supertypes
     */
    public Type(String name, Type... supertypes)
    {
        this(name);
        if(supertypes.length != 0) {
            this.superTypes = supertypes;
        }
    }
    
    public Type(String name, String description)
    {
        this(name);
        this.description = description;
    }
    
    public Type(String name, String description, Type... supertypes)
    {
        this(name, supertypes);
        this.description = description;
    }
    
    /**
     * @param description
     *            a new description for this type
     */
    public void setDescription(String description)
    {
        this.description = description;
    }
    
    /**
     * @return a descrition of this type
     */
    public String getDescription()
    {
        return description;
    }
    
    /**
     * @return the name for this Type
     */
    @Override
    public String getName()
    {
        return name;
    }
    
    @Override
    public Type getType()
    {
        return this;
    }
    
    public boolean hasSuperType()
    {
        return superTypes != null && superTypes.length > 0;
    }
    
    public ImmutableList<Type> getSuperTypes()
    {
        return ImmutableList.copyOf(superTypes);
    }
    
    
    
    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode()
    {
        return id.hashCode();
    }
    
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj)
    {
        if(this == obj) {
            return true;
        }
        if(obj == null) {
            return false;
        }
        if(!(obj instanceof Type)) {
            return false;
        }
        Type other = (Type) obj;
        if(!id.equals(other.id)) {
            return false;
        }
        return true;
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString()
    {
        return name;
    }
    
    public boolean isSubtypeOf(Type type)
    {
        if(this.equals(type)) {
            return true;
        }
        for(int i = 0; i < superTypes.length; i++) {
            if(superTypes[i].isSubtypeOf(type)) {
                return true;
            }
        }
        return false;
    }
    
    @Override
    public UUID getId()
    {
        return id;
    }
}
